﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CalculatorLibrary.Computation
{
    public class Computation
    {
        #region Fields
        public ComputationTree root;
        public List<String> paranExpressions;
        public List<ComputationTree> paranTrees;
        public decimal[] paranValues;
        public int paranCounter;
        public int isInvalid;
        #endregion //Fields

        #region Format Functions
        //Format the Expressions so it can be properly processed by the insert Function
        public void FormatExpression(String expression)
        {
            isInvalid = 0;
            root = new ComputationTree();

            ReplaceExpression(ref expression);

            //Run the expression for paranthesis
           isInvalid = CheckForParanthesis(ref expression);

           if (isInvalid == 1)
               return;

            isInvalid = root.Insert(ref root, expression);
        }


        /*
                Switch different operations that are more than one word to single letters for our
                tree operation: the following are being converted over into the following letters:
                    sin = s
                    cos = d
                    tan = t
                    mod = m
                    ln = n
                    log = l
                    cot = o
                    tanh = h
                    sec = f
                    cosh = i
                    csc = a
                    sinh = r
                    negative = g
            
                Added the following for the Unit Tests:
                    sqrt = √
                    pi = π
          
        */
        public string ReplaceExpression(ref string expression)
        {
            expression = expression.Replace("sin", "s");
            expression = expression.Replace("cos", "d");
            expression = expression.Replace("tan", "t");
            expression = expression.Replace("mod", "m");
            expression = expression.Replace("ln", "n");
            expression = expression.Replace("log", "l");
            expression = expression.Replace("cot", "o");
            expression = expression.Replace("tanh", "h");
            expression = expression.Replace("sec", "f");
            expression = expression.Replace("cosh", "i");
            expression = expression.Replace("csc", "a");
            expression = expression.Replace("sinh", "r");
            expression = expression.Replace("sqrt", "√");
            expression = expression.Replace("pi", "π");

            // Change ² to ^2 (this way when we add in Powers it's already set for us).
            expression = expression.Replace("²", "^2");

            // Set up the sqrt.
            int i = 0;
            while ((i = expression.IndexOf('√', i)) != -1)
            {
                // Add a 2 before the root if a number isnt already before it.
                if(i == 0 || char.IsNumber(expression[i-1]) == false)
                {
                    // Set up a StringBuilder.
                    StringBuilder sb = new StringBuilder(expression);

                    // Insert a 2 infront of the root symbol.
                    sb.Insert(i, '2');
                    i += 2;

                    expression = sb.ToString();
                }

                // Increase i, so we aren't still at the root symbol.
                i++;
            }

            // Set up for the negative.
            i = 0;
            while ((i = expression.IndexOf('-', i)) != -1)
            {
                if(i == 0 || char.IsNumber(expression[i-1]) == false)
                {
                    // Set up a StringBuilder.
                    StringBuilder sb = new StringBuilder(expression);

                    // Inset g at the position that the negative symbol is and remove the negative symbol.
                    sb.Insert(i, 'g');
                    sb.Remove(i + 1, 1);
                    expression = sb.ToString();

                    i++;
                }

                i++;
            }

            return expression;
        }
        #endregion //Format Functions

        #region Evaluate
        //Evaluate the expression based on what is in the node that was setup by the Insert Function
        public decimal Evaluate(ComputationTree node)
        {
            decimal convertToRad = IsDegrees();
            Console.WriteLine("The number is {0}", convertToRad);
            switch (node.valueNode.Type)
            {
                case '+':
                    return Evaluate(node.left) + Evaluate(node.right);

                case '-':
                    return Evaluate(node.left) - Evaluate(node.right);

                case '*':
                    return Evaluate(node.left) * Evaluate(node.right);

                case '/':
                    return Evaluate(node.left) / Evaluate(node.right);

                case '√':
                    return (decimal)Math.Pow((double)Evaluate(node.right), (double)(1/Evaluate(node.left)));

                case '^':
                    return (decimal)Math.Pow((double)Evaluate(node.left), (double)Evaluate(node.right));

                case '%':
                    return Evaluate(node.left) / 100;

                case '!':
                    Decimal evaluateNumber = Evaluate(node.left);

                    // Check to see if the left side is less than Zero.
                    if (evaluateNumber < 0)
                        isInvalid = 1;
                    return Factorial(evaluateNumber);

                case 's':
                    return FormatLargeNumbers(Math.Sin((double)Evaluate(node.right) * (double)convertToRad));
                
                case 'd':
                    return FormatLargeNumbers(Math.Cos((double)Evaluate(node.right) * (double)convertToRad));

                case 't':
                    return FormatLargeNumbers(Math.Tan((double)Evaluate(node.right) * (double)convertToRad));

                case 'm':
                    return Evaluate(node.left) % Evaluate(node.right);

                case 'n':
                    return FormatLargeNumbers(Math.Log((double)Evaluate(node.right)));

                case 'l':
                    return FormatLargeNumbers(Math.Log10((double)Evaluate(node.right)));

                case 'o':
                    return FormatLargeNumbers(1 / Math.Tan((double)Evaluate(node.right) * (double)convertToRad));

                case 'h':
                    return FormatLargeNumbers(Math.Tanh((double)Evaluate(node.right) * (double)convertToRad));

                case 'f':
                    return FormatLargeNumbers(1 / Math.Cos((double)Evaluate(node.right) * (double)convertToRad));

                case 'i':
                    return FormatLargeNumbers(Math.Cosh((double)Evaluate(node.right) * (double)convertToRad));

                case 'a':
                    return FormatLargeNumbers(1 / Math.Sin((double)Evaluate(node.right) * (double)convertToRad));

                case 'r':
                    return FormatLargeNumbers(Math.Sinh((double)Evaluate(node.right) * (double)convertToRad));

                case 'p':
                    return paranValues[(int)node.valueNode.Value];

                case 'g':
                    return (-Evaluate(node.right));

                case 'c':
                    return node.valueNode.Value;
            }

            return -1;
        }
        #endregion //Evaluate

        #region Paranthesis Functions
        //Function used to check and setup paranthesis cases
        public int CheckForParanthesis(ref String expression)
        {
            //Verify to see if we even have a paranthesis
            if (!expression.Contains('('))
                return 0;

            //Check if there are not an equal amount of parantheses
            if(GetNumberOfOccurrences(expression, '(') != GetNumberOfOccurrences(expression, ')'))
                return 1;

            paranExpressions = new List<string>();
            paranTrees = new List<ComputationTree>();

            while (expression.Contains('('))
            {
                //Grab first occurence of paranthesis
                int firstPosition = expression.IndexOf('(');

                //Call our function to deal with all our paranthesis
              FindInnerParanthesis(ref expression, firstPosition);
            }

                //Create the trees for the paranthesis expressions
                foreach (String currExpression in paranExpressions)
                {
                    ComputationTree insertTree = new ComputationTree();
                   isInvalid = insertTree.Insert(ref insertTree, currExpression);
                   //In case any of the trees are invalid
                   if (isInvalid == 1)
                       return 1;
                    paranTrees.Add(insertTree);
                }

                //Evaluate and store the values returned from the trees
                paranValues = new decimal[paranExpressions.Count];
                int counter = 0;

                foreach (ComputationTree currTree in paranTrees)
                {
                    paranValues[counter] = Evaluate(currTree);
                    counter++;
                }

            return 0;

        }

        //Used to handle inner paranthesis
        public void FindInnerParanthesis(ref String expression, int firstPosition)
        {
            Stack<int> startParan = new Stack<int>();

            int i = 0;
            int currPosition = firstPosition;

            //Used for when we have a pair of matching parantheses
            int matchStart = -1;
            int matchEnd = -1;

            startParan.Push(firstPosition);

            do
            {
                //Search for first end parantheses
                for (i = currPosition+1; i < expression.Length; i++)
                {
                    //If we hit a beginning parantheses
                    if (expression[i] == '(')
                    {
                        startParan.Push(i);
                    }
                    
                    //Found the matching end parantheses
                    else if(expression[i] == ')')
                    {
                        matchStart = startParan.Pop();
                        matchEnd = i;
                        break;
                    }
                }


                //Store the expression within matching start and end paranthesis
                if (matchStart != -1 && matchEnd != -1)
                {
                    String storingString = expression.Substring(matchStart+1, (matchEnd-(matchStart+1)));
                    String replaceString = expression.Substring(matchStart, (matchEnd - matchStart)+1);

                    //Add expression to Expression list and Replace Parantheses statement with P + counter placeholder
                    paranExpressions.Add(storingString);
                    expression = expression.Replace(replaceString, "p" + paranCounter);
                    paranCounter++;

                    //Reset matchStart and matchEnd to verify that we had a match
                    matchStart = -1;
                    matchEnd = -1;
                }

               //Update the current position for the loop to the top of the stack 
               if(startParan.Count != 0)
                currPosition = startParan.Peek();
            } while (startParan.Count > 0);
        }

        //Find the number of occurrences in a string
        public int GetNumberOfOccurrences(String stringToBeChecked, char target)
        {
            int count;
            count = 0;
            for (int i = 0; i < stringToBeChecked.Length; i++)
            {
                if (stringToBeChecked[i] == target)
                    count++;
            }

            return count;
        }
        #endregion //Paranthesis Functions

        #region FormatLargeNumbers
        //Return 0 if number is less than 0.0000000001(used to properly display large numbers if they are encountered)
        public decimal FormatLargeNumbers(double input)
        {
            if (Math.Abs(input) < 0.0000000001)
                return 0;

            return (decimal)input;
        }
        #endregion //FormatLargeNumbers

        #region Factorial
        //Used to evaluate the Factorial of a number
        public Decimal Factorial(Decimal num)
        {
            if (num <= 0)
                return 0;

            if (num == 1)
                return 1;

            return num * Factorial(num-1);
        }
        #endregion //Factorial

        #region Properties
        // To help determine if we are in Radians or Degrees.
        public string AngleType { get; set; }
        #endregion //Properties

        #region IsDegrees
        //Return conversion if the value is set to Degrees
        public decimal IsDegrees()
        {
            if(AngleType == "Degrees")
                return (decimal)Math.PI / 180;

               return 1;
        }
        #endregion //IsDegrees
    }
}
